package com.cloudfast.dao.impl;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cloudfast.common.date.DateUtils;
import com.cloudfast.common.jdbc.reflect.ReflectUtils;
import com.cloudfast.common.page.Page;
import com.cloudfast.dao.SimpleRepository;
import com.cloudfast.dao.conf.DataBaseSource;
import com.cloudfast.dao.conf.EntityParseInfo;
import com.cloudfast.entity.BaseEntity;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

/**
 * 简化CURD 实践
 *
 * @author liuyw
 * @date 2022年9月7日
 */
public class SimpleRepositoryImpl implements SimpleRepository {

    private static final Logger logger = LoggerFactory.getLogger(Logger.class);

    private DataBaseSource databaseSource;

    @SuppressWarnings("rawtypes")
    private ReflectUtils reflectUtils = new ReflectUtils<>();
    private Gson gson = null;

    public SimpleRepositoryImpl(DataBaseSource databaseSource) {
        this.databaseSource = databaseSource;
        gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends BaseEntity> void saveOrUpdate(T entity, EntityParseInfo epf) {
        try {
            Field f = entity.getClass().getDeclaredField(epf.getPrimaryFieldKey());
            f.setAccessible(true);
            String subTableName = epf.getTableName();
            boolean isNewAdd = true;
            if (epf.isSubTable()) {
                // 创建分表
                subTableName = reflectUtils.findSubTableName(entity);
                StringBuilder createTableSQL = new StringBuilder("CREATE TABLE  If Not Exists " + subTableName
                        + " SELECT * FROM " + subTableName + " WHERE 1=2 ");
                nativeExcute(createTableSQL.toString(), null);
            }
            Object id = reflectUtils.getPrimaryKeyValue(entity);
            Map<String, Object> sqls = reflectUtils.repositorySQL(entity, subTableName, epf.getPrimaryColumnKey());
            String sql = sqls.get("insert").toString();
            if (id != null && findOne(id.toString(), epf) != null) {
                sql = sqls.get("update").toString();
                isNewAdd = false;
            }
            logger.info("saveOrUpdate>>>SQL:" + sql);
            logger.info("saveOrUpdate>>>参数:" + sqls.get("params"));

            nativeExcute(sql, (Map<String, Object>) sqls.get("params"));
            if (isNewAdd) {
                T newEntity = (T) findEntity(subTableName, sqls.get("where").toString(),
                        (Map<String, Object>) sqls.get("params"), epf.getSelectKey(), entity.getClass());
                Object newEntityKey = reflectUtils.getPrimaryKeyValue(newEntity);
                if (f.getType().getName().endsWith("Long")) {
                    id = Long.parseLong(newEntityKey.toString());
                } else if (f.getType().getName().endsWith("Integer")) {
                    id = Integer.parseInt(newEntityKey.toString());
                } else {
                    id = newEntityKey.toString();
                }
                f.set(entity, id);
            }

            // selfGrade 处理
            Object parentId = reflectUtils.getGradeParentId(entity);
            if (parentId != null && StringUtils.isNotEmpty(parentId + "")) {
                T parent = findOne(parentId.toString(), epf);
                Object oGrade = "-";
                if (parent != null) {
                    oGrade = reflectUtils.getParentGradeCode(parent);
                }
                Map<String, Object> fillEntity = reflectUtils.fillGradeCode(entity, oGrade.toString());
                if ((boolean) fillEntity.get("update")) {
                    saveOrUpdate((T) fillEntity.get("data"), epf);
                }
            }

        } catch (Exception e) {
            logger.error(e.getMessage());
        }

    }

    @Override
    public <T extends BaseEntity> T findOne(Long id, EntityParseInfo ifno) {
        return findOne(id + "", ifno);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends BaseEntity> T findOne(String id, EntityParseInfo ifno) {
        try {
            StringBuilder hql = new StringBuilder();
            hql.append("select ").append(ifno.getSelectKey());
            hql.append(" from ").append(ifno.getTableName());
            hql.append(" where 1=1  and ").append(ifno.getPrimaryColumnKey());
            hql.append(" = ?");
            Map<String, Object> params = new LinkedHashMap<>();
            params.put("id", id);
            List<Map<String, Object>> list = queryList(hql.toString(), params);
            if (list != null && list.size() == 1) {
                return (T) gson.fromJson(new JSONObject(list.get(0)).toString(), ifno.getClazz());

            } else if (list != null && list.size() > 1) {
                throw new RuntimeException("发现多条记录");
            }
        } catch (Exception e) {
            throw new RuntimeException("系统内部异常");
        }
        return null;
    }

    @Override
    public <T extends BaseEntity> void delete(Long id, EntityParseInfo ifno) {
        delete(id + "", ifno);
    }

    @Override
    public <T extends BaseEntity> void delete(String id, EntityParseInfo ifno) {
        StringBuilder hql = new StringBuilder();
        hql.append("delete ").append(" from ").append(ifno.getTableName());
        if (ifno.getDeleteKeys() != null) {
            hql = new StringBuilder();
            hql.append("update ").append(ifno.getTableName());
            hql.append(" set  ");
            int i = 0;
            for (String key : ifno.getDeleteKeys().keySet()) {
                if (i > 0) {
                    hql.append(",");
                }
                hql.append(ReflectUtils.fieldNameToDataBaseName(key)).append(" = '1'");
                i++;
            }
        }
        hql.append(" where 1=1  and ").append(ifno.getPrimaryColumnKey());
        hql.append(" = ?");
        Map<String, Object> params = new LinkedHashMap<>();
        params.put("id", id);
        logger.info(hql.toString());
        nativeExcute(hql.toString(), params);

    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends BaseEntity> Page<T> nativeQueryPage(Page<T> page, T entity, EntityParseInfo ifno) {
        try {
            StringBuilder hql = new StringBuilder();
            hql.append("select ").append(ifno.getSelectKey());
            hql.append(" from ").append(ifno.getTableName());
            hql.append(" where 1=1 ");
            Map<String, Object> params = reflectUtils.findEntityParams(entity);
            for (String key : params.keySet()) {
                hql.append(" and ").append(ReflectUtils.fieldNameToDataBaseName(key)).append(" = ? ");
            }
            hql.append("limit ").append((page.getPageNo() - 1) * page.getPageSize()).append(",");
            hql.append(page.getPageSize());
            StringBuilder countSQL = new StringBuilder("select count(");
            countSQL.append(ReflectUtils.getPrimaryKeyColumn(entity.getClass())).append(") count from ");
            countSQL.append(hql.toString().split("from")[1].split(" limit ")[0]);
            List<Map<String, Object>> resultData = queryList(hql.toString(), params);
            page.setTotal(Integer.parseInt(queryList(countSQL.toString(), params).get(0).get("count").toString()));
            if (resultData != null && resultData.size() > 0) {
                JSONArray dataArray = new JSONArray(resultData);
                Type lisType = new TypeToken<LinkedList<T>>() {
                }.getType();
                List<T> rows = gson.fromJson(dataArray.toString(), lisType);
                page.setRows(rows);
            }
            return page;
        } catch (Exception e) {
            logger.error(e.getMessage());
        }

        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends BaseEntity> List<T> nativeQueryList(T entity, EntityParseInfo ifno) {
        try {
            StringBuilder hql = new StringBuilder();
            hql.append("select ").append(ifno.getSelectKey());
            hql.append(" from ").append(ifno.getTableName());
            hql.append(" where 1=1 ");
            Map<String, Object> params = reflectUtils.findEntityParams(entity);
            for (String key : params.keySet()) {
                hql.append(" and ").append(ReflectUtils.fieldNameToDataBaseName(key)).append(" = ? ");
            }
            List<Map<String, Object>> resultData = queryList(hql.toString(), params);
            if (resultData != null && resultData.size() > 0) {
                JSONArray dataArray = new JSONArray(resultData);
                Type lisType = new TypeToken<LinkedList<T>>() {
                }.getType();
                List<T> rows = gson.fromJson(dataArray.toString(), lisType);
                return rows;
            }
        } catch (Exception e) {
            // TODO: handle exception
        }

        return null;
    }

    /**
     * 执行sql
     *
     * @param hql
     * @param params
     */
    private void nativeExcute(String hql, Map<String, Object> params) {
        try (Connection con = databaseSource.getConnection();
             PreparedStatement pst = con.prepareStatement(hql, Statement.RETURN_GENERATED_KEYS);) {
            setParams(pst, params);
            pst.executeUpdate();
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * 设置参数
     *
     * @param pst
     * @param params
     */
    private void setParams(PreparedStatement pst, Map<String, Object> params) {
        if (params != null) {
            int i = 1;
            try {
                for (String key : params.keySet()) {
                    if (params.get(key) instanceof java.lang.Integer) {
                        pst.setInt(i, Integer.parseInt(params.get(key).toString()));
                    } else if (params.get(key) instanceof java.lang.Long) {
                        pst.setInt(i, Integer.parseInt(params.get(key).toString()));
                    } else if (params.get(key) instanceof java.lang.Double) {
                        pst.setDouble(i, Double.parseDouble(params.get(key).toString()));
                    } else if (params.get(key) instanceof java.util.Date) {
                        pst.setString(i, params.get(key).toString());
                    } else {
                        pst.setString(i, params.get(key).toString());
                    }
                    i++;
                }
            } catch (Exception e) {
                logger.error(e.getMessage());
            }
        }
    }

    private <T> T findEntity(String tableName, String where, Map<String, Object> params, String selectColumns,
                             Class<T> mEntityClass) throws Exception {
        StringBuilder sql = new StringBuilder("select ");
        sql.append(selectColumns).append(" from ").append(tableName);
        sql.append(" where ").append(where);
        logger.info("findEntity>>>SQL:" + sql.toString());
        logger.info("findEntity>>>参数:" + params);
        List<Map<String, Object>> list = queryList(sql.toString(), params);
        if (list != null && list.size() > 0) {
            return gson.fromJson(new JSONObject(list.get(0)).toString(), mEntityClass);
        }
        return null;
    }

    /**
     * 查询列表
     *
     * @param sql
     * @param params
     * @return
     * @throws Exception
     */
    private List<Map<String, Object>> queryList(String sql, Map<String, Object> params) throws Exception {
        List<Map<String, Object>> list = null;

        try (Connection con = databaseSource.getConnection();
             PreparedStatement pst = con.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE,
                     ResultSet.CONCUR_UPDATABLE);) {
            setParams(pst, params);
            try (ResultSet rs = pst.executeQuery()) {
                ResultSetMetaData md = rs.getMetaData();
                // 返回此 ResultSet 对象中的列数
                int columnCount = md.getColumnCount();
                if (rs.next()) {
                    rs.beforeFirst();
                    list = new ArrayList<>();
                    while (rs.next()) {
                        Map<String, Object> rowData = new HashMap<>(columnCount);
                        if (columnCount == 1) {
                            if (rs.getObject(1) == null) {
                                list = null;
                            } else {
                                rowData.put(md.getColumnName(1), rs.getObject(1));
                                if (list != null) {
                                    list.add(rowData);
                                }
                            }
                        } else {
                            for (int i = 1; i <= columnCount; i++) {
                                if (rs.getObject(i) != null) {
                                    rowData.put(md.getColumnName(i), rs.getObject(i));
                                    if (rs.getObject(i).getClass().getTypeName().equals("java.time.LocalDateTime")) {
                                        String columnValaue = rs.getObject(i).toString().replace("T", " ");
                                        if (columnValaue.length() < 19) {
                                            columnValaue = columnValaue + ":00";
                                        }
                                        columnValaue = DateUtils.dateToString(
                                                DateUtils.stringToDate(columnValaue, DateUtils.DATETIMEFORMAT),
                                                DateUtils.DATETIMEFORMAT);
                                        rowData.put(md.getColumnName(i), columnValaue);
                                    }

                                } else {
                                    rowData.put(md.getColumnName(i), "");
                                }
                            }
                            if (list != null) {
                                list.add(rowData);
                            }
                        }
                    }
                }
                rs.close();

            }

        }
        return list;
    }

}
